﻿using System;
using System.Collections.Generic;
using System.Text;
using Studio.Network.Utilities;
using System.Drawing;
using System.Drawing.Imaging;
using Studio.Foundation.ImageBox;
using Studio.Windows;
using Studio.Algorithm.SVM;
using System.IO;

namespace Studio.Network.Kaixin001
{
    public class KXCodeImage
    {

        #region Properties
        
        public static string DEFAULT_SVM_FILE = "KXCodeImage_SVM.txt";

        string _svmDataFile = KXCodeImage.DEFAULT_SVM_FILE;

        private int _sample_width = 25;
        private int _sample_height = 25;

        Dictionary<char, double> _dict = new Dictionary<char, double>();
        List<Node[]> _x = new List<Node[]>();
        List<double> _y = new List<double>();

        Model _svm = null;
        RangeTransform _range = null;
        Problem _problem = null;

                
        public string SVMDataFile
        {
            get { return _svmDataFile; }
            set { _svmDataFile = value; }
        }

        public List<Node[]> X
        {
            get { return _x; }
        }

        public List<double> Y
        {
            get { return _y; }
        }

        public Model SVM
        {
            get { return _svm; }
        }

        public Dictionary<char, double> CharDictionary
        {
            get { return _dict; }
        }

        public int SampleWidth
        {
            get { return this._sample_width; }
            set { this._sample_width = value; }
        }

        public int SampleHeight
        {
            get { return _sample_height; }
            set { _sample_height = value; }
        }
        public int Dimension
        {
            get { return this._sample_width + this._sample_height; }
        }

        #endregion

        public KXCodeImage()
        {
            this.Initialize(KXCodeImage.DEFAULT_SVM_FILE);
        }

        public KXCodeImage(string svmFile)
        {
            this.Initialize(svmFile);
        }

        void Initialize(string svmFile)
        {
            _dict.Clear();

            char c = '0';
            while (c <= '9') _dict.Add(c, c++ - '0');

            c = 'a';
            while (c <= 'z') _dict.Add(c, c++ - 'a' + 10);

            c = 'A';
            while (c <= 'Z') _dict.Add(c, c++ - 'A' + 10 + 26);

            this._svmDataFile = svmFile;
            
            if (false == string.IsNullOrEmpty(svmFile))
            {
                this.LoadSVM(svmFile);
            }
        }

        public Bitmap RemoveFrame(Bitmap bitmap)
        {
            int width = bitmap.Width;
            int height = bitmap.Height;

            Bitmap b = new Bitmap(bitmap.Clone() as Bitmap);
            BitmapData data = b.LockBits(new Rectangle(0, 0, width, height),
                ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

            unsafe
            {
                byte* p = (byte*)data.Scan0;
                byte* q = (byte*)data.Scan0;
                int offset = data.Stride - width * GrayBox.BPP(b);

                q += GrayBox.BPP(b) * (width - 1);
                for (int y = 0; y < height; y++)
                {
                    p[0] = p[1] = p[2] = p[3] = 255;
                    q[0] = q[1] = q[2] = q[3] = 255;
                    p += (GrayBox.BPP(b) * width + offset);
                    q += (GrayBox.BPP(b) * width + offset);
                }

                p = (byte*)data.Scan0;
                q = (byte*)data.Scan0;
                q += (GrayBox.BPP(b) * width + offset) * (height - 1);
                for (int x = 0; x < width; x++)
                {
                    p[0] = p[1] = p[2] = p[3] = 255;
                    q[0] = q[1] = q[2] = q[3] = 255;
                    p += GrayBox.BPP(b);
                    q += GrayBox.BPP(b);
                }
            }
            b.UnlockBits(data);
            return b;
        }

        public Bitmap[] Split(Bitmap b)
        {
            int w = b.Width / 4;
            int h = b.Height;
            Bitmap[] bArray = new Bitmap[4];

            BitmapData data = b.LockBits(new Rectangle(0, 0, w, h),
                    ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

            unsafe
            {
                byte* q = (byte*)data.Scan0;

                for (int i = 0; i < 4; i++)
                {
                    bArray[i] = new Bitmap(w, h, data.Stride, PixelFormat.Format32bppArgb, (IntPtr)(q));
                    q += (GrayBox.BPP(b) * w);
                }
            }
            b.UnlockBits(data);

            return bArray;

        }


        private Bitmap Shrink(Bitmap b)
        {
            int threshold;
            return this.Shrink(b, out threshold);
        }

        public Bitmap Shrink(Bitmap b, out int threshold)
        {
            threshold = 0;
            Bitmap bc = b.Clone() as Bitmap;
            Bitmap ba = GrayBox.AutoFitThreshold(bc, out threshold);
            Bitmap b1 = ba.Clone() as Bitmap;
            int oldThreshold = threshold;
            int w = b.Width;
            int h = b.Height;

            int min = 25;
            int max = byte.MaxValue;

            while (max > min)
            {
                int sw = SolidWidth(b1);
                int sh = SolidHeight(b1);

                int bw = InRangeWidth(sw, w);
                int bh = InRangeHeight(sh, h);


                // Succeed or Failed.
                if (0 == bw + bh)
                {
                    if (bw != 0)
                    {
                        if (20 < oldThreshold && oldThreshold < 200)
                            return ba;
                        else
                        {
                            threshold = 115;
                            bc = b.Clone() as Bitmap;
                            b1 = GrayBox.Thresholding(bc, (byte)threshold);
                        }
                    }
                    break;
                }

                // Great or Less ?
                if (bw + bh < 0)
                {
                    min = threshold + 1;
                    threshold = (threshold + max) / 2;
                }
                else
                {
                    max = threshold - 1;
                    threshold = (threshold + min) / 2;
                }
                bc = b.Clone() as Bitmap;
                b1 = GrayBox.Thresholding(bc, (byte)threshold);
            }
            return b1;
        }


        public Bitmap CenterImage(Bitmap b, int width, int height)
        {
            int srcLeft, srcTop;
            int dstLeft, dstTop;
            int sw = SolidWidth(b, out srcLeft);
            int sh = SolidHeight(b, out srcTop);

            // crop width ?
            if (sw > width)
            {
                dstLeft = 0;
                srcLeft += ((sw - width) / 2);
            }
            else
            {
                dstLeft = ((width - sw) / 2);
            }
            // crop height ?
            if (sh > height)
            {
                dstTop = 0;
                srcTop += ((sh - height) / 2);
            }
            else
            {
                dstTop = ((height - sh) / 2); ;
            }

            int w = Math.Min(sw, width);
            int h = Math.Min(sh, height);

            Bitmap centered = new Bitmap(width, height, PixelFormat.Format32bppArgb);

            Graphics g = Graphics.FromImage(centered);
            g.FillRectangle(Brushes.White, new Rectangle(0, 0, width, height));
            g.DrawImage(b, dstLeft, dstTop, new RectangleF(srcLeft, srcTop, sw, sh), GraphicsUnit.Pixel);

            return centered;
        }

        public Bitmap MaxConnectedGraph(Bitmap b)
        {
            bool[,] SubGraph;
            return this.MaxConnectedGraph(b, out SubGraph);
        }

        public Bitmap MaxConnectedGraph(Bitmap b, out bool[,] SubGraph)
        {
            this.LockBitmapToCache(b);

            int w = b.Width;
            int h = b.Height;
            // initialized to be false
            bool[,] Searched = new bool[w, h];
            // int[] x_offset = new int[] { 0, 0, -1, 1 };
            // int[] y_offset = new int[] { 1, -1, 0, 0 };
            // int[] x_offset = new int[] { 0,  0,  1,  1,  1, -1, -1, -1 };
            // int[] y_offset = new int[] { 1, -1,  0, -1,  1,  0, -1,  1 };
            int distance = 6;
            List<int> x_offsetList = new List<int>();
            List<int> y_offsetList = new List<int>();
            for (int x1 = -distance; x1 < distance; x1++)
            {
                for (int x2 = -distance; x2 < distance; x2++)
                {
                    int d = (x2 - x1) * (x2 - x1);
                    if (d < distance * distance && d > 0)
                    {
                        x_offsetList.Add(x1);
                        y_offsetList.Add(x2);
                    }
                }
            }
            int[] x_offset = x_offsetList.ToArray();
            int[] y_offset = y_offsetList.ToArray();

            int n = 0;
            Color sample;
            SubGraph = null;
            for (int x = 0; x < w; x++)
            {
                for (int y = 0; y < h; y++)
                {
                    if (false == Searched[x, y])
                    {
                        // initialze value of a bool array is ALL false
                        bool[,] g = new bool[w, h];
                        Color s = this.UnsafeGetPixel(b, x, y);
                        int c = ConnectedGraph(b, x, y, Searched, g, s, x_offset, y_offset);
                        if (c > n && c > 12)
                        {
                            int sw = SolidWidth(g);
                            int sh = SolidHeight(g);
                            // height greater than 70% of the width
                            bool match = sh > (sw * 7 / 10);
                            if (0 == InRangeWidth(sw, w) && 0 == InRangeHeight(sh, h) && match)
                            {
                                n = c;
                                sample = s;
                                SubGraph = g;
                            }
                        }
                    }
                }
            }

            this.UnLockCachedBitmap();
            return GrayBox.BinaryImage(SubGraph, Color.Black, Color.White); 
        }

        int ConnectedGraph(Bitmap b, int x, int y, bool[,] Searched, bool[,] SubGraph, Color sample, int[] x_offset, int[] y_offset)
        {
            Searched[x, y] = true;
            SubGraph[x, y] = true;

            int directions = x_offset.Length;
            int w = b.Width;
            int h = b.Height;
            // this pixel
            int n = 1;

            for (int i = 0; i < directions; i++)
            {
                int cx = x + x_offset[i];
                int cy = y + y_offset[i];
                if (cx < 0 || cx >= w || cy < 0 || cy >= h) continue;
                if (true == Searched[cx, cy]) continue;
                Color c = UnsafeGetPixel(b, cx, cy);
                if (true == ColorSimilar(c, Color.White))
                {
                    Searched[x, y] = true;
                    continue;
                }
                if (true == ColorSimilar(c, sample))
                {
                    n += ConnectedGraph(b, cx, cy, Searched, SubGraph, sample, x_offset, y_offset);
                }
            }
            return n;
        }


        Bitmap _lockedBitmap = null;
        BitmapData _lockedBitmapData = null;

        unsafe void LockBitmapToCache(Bitmap b)
        {
            if (false == b.Equals(_lockedBitmap))
            {
                this.UnLockCachedBitmap();
                _lockedBitmap = b;
                _lockedBitmapData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
                    ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            }
        }

        unsafe void UnLockCachedBitmap()
        {
            if (_lockedBitmap != null)
            {
                _lockedBitmap.UnlockBits(_lockedBitmapData);
            }
            _lockedBitmap = null;
            _lockedBitmapData = null;
        }

        unsafe Color UnsafeGetPixel(Bitmap b, int cx, int cy)
        {
            if (null == b) throw new ArgumentNullException("b");
            int w = b.Width;
            int h = b.Height;
            if (false == b.Equals(_lockedBitmap))
            {
                this.UnLockCachedBitmap();
                this.LockBitmapToCache(b);
            }

            int bpp = GrayBox.BPP(b);
            byte* p = (byte*)_lockedBitmapData.Scan0;
            int offset = _lockedBitmapData.Stride - w * bpp;
            p += ((w * bpp + offset) * (cy - 1) + cx * bpp);
            return Color.FromArgb(p[3], p[2], p[1], p[0]);
        }

        ~KXCodeImage()
        {
            this.UnLockCachedBitmap();
        }

        public static bool ColorSimilar(Color a, Color b)
        {
            return (a.R == b.R && a.G == b.G && a.B == b.B);
        }

        static int InRangeWidth(int a, int b)
        {
            bool b1 = (a >= 3);
            bool b2 = (a <= 3 * b / 4);

            if (b1 && b2) return 0;
            if (false == b1) return -1;
            return 1;
        }

        static int InRangeHeight(int a, int b)
        {
            bool b1 = (a >= b / 4);
            bool b2 = (a <= b / 2);

            if (b1 && b2) return 0;
            if (false == b1) return -1;
            return 1;
        }

        static int SolidWidth(bool[,] binaryArray)
        {
            return SolidRange(binaryArray, 1);
        }

        static int SolidHeight(bool[,] binaryArray)
        {
            return SolidRange(binaryArray, 0);
        }

        static int SolidRange(bool[,] binaryArray, int dimension)
        {
            int Count = binaryArray.GetLength(1 - dimension);
            int h = binaryArray.GetLength(dimension);
            int w = binaryArray.GetLength(1 - dimension);
            int i = 0;
            while (i < Count && true == IsAllFalse(binaryArray, dimension, i)) i++;
            int j = Count - 1;
            while (j >= i && true == IsAllFalse(binaryArray, dimension, j)) j--;
            return (j - i + 1);
        }

        static bool IsAllFalse(bool[,] binaryArray, int dimension, int fixedIndex)
        {
            int x = binaryArray.GetLength(dimension);
            int i = 0;
            switch (dimension)
            { 
                case 0:
                    while (i < x && false == binaryArray[i, fixedIndex]) i++;
                    break;
                case 1:
                    while (i < x && false == binaryArray[fixedIndex, i]) i++;
                    break;
            }
            return x == i;
        }

        static int SolidWidth(Bitmap b)
        {
            int i;
            return SolidWidth(b, out i);
        }

        static int SolidWidth(Bitmap b, out int i)
        {
            EdgeHistogram histogram = new EdgeHistogram(b, 128);
            int[] Levels = histogram.Horizontal.Value;
            int Count = b.Width;
            int Value = b.Height;
            i = 0;
            while (i < Count && Levels[i] == Value) i++;
            int j = Count - 1;
            while (j >= i && Levels[j] == Value) j--;
            return (j - i + 1);
        }

        static int SolidHeight(Bitmap b)
        {
            int i;
            return SolidHeight(b, out i);
        }

        static int SolidHeight(Bitmap b, out int i)
        {
            EdgeHistogram histogram = new EdgeHistogram(b, 128);
            int[] Levels = histogram.Vertical.Value;
            int Count = b.Height;
            int Value = b.Width;
            i = 0;
            while (i < Count && Levels[i] == Value) i++;
            int j = Count - 1;
            while (j >= i && Levels[j] == Value) j--;
            return (j - i + 1);
        }


        /////////////////////////////////////////////////
        /// SVM Code starts here.
        /////////////////////////////////////////////////

        Random _random = new Random(DateTime.Now.Millisecond);

        public string Predict(Bitmap b)
        {
            if (null == b || null == this._svm) return null;
            // Remove 
            b = this.RemoveFrame(b);
            Bitmap[] bmpList = this.Split(b);

            string result = string.Empty;

            foreach (Bitmap bmp in bmpList)
            {
                Bitmap code = this.MaxConnectedGraph(bmp);
                // 2nd chance
                if (null == code) code = this.Shrink(bmp);
                // Center Image
                Bitmap centered = this.CenterImage(code, this._sample_width, this._sample_height);
                // Get Sample Vector
                Node[] vector = this.Sampling(centered);
                char c = this.Predict(vector);
                // Make a gamble if predict failed.
                if('\0' == c)
                {
                    int index = _random.Next(this._dict.Keys.Count);
                    int counter = 0;
                    foreach(char ch in this._dict.Keys)
                        if (counter++ == index) result += ch;
                }
                else
                {
                    result += c;
                }
            }
            return result;
        }

        public Node[] Sampling(Bitmap b)
        {
            if (b.Width != this._sample_width || b.Height != this._sample_height)
                return null;

            Node[] vector = new Node[this.Dimension];
            EdgeHistogram histogram = new EdgeHistogram(b, 128);
            for (int x = 0; x < this._sample_width; x++)
            {
                vector[x] = new Node(x + 1, histogram.Horizontal.Value[x]);
            }
            for (int x = 0; x < this._sample_height; x++)
            {
                vector[x + this._sample_width] = new Node(x + 1 + this._sample_width, histogram.Vertical.Value[x]);
            }
            return vector;
        }

        public char Predict(Node[] vector)
        {
            if (null != _svm && null != vector)
            {
                Node[] x = _range.Transform(vector);
                double r = Prediction.Predict(_svm, x);
                foreach (char c in _dict.Keys)
                    if (_dict[c] == r)
                        return c;
            }
            return '\0';
        }

        public void AddSample(Node[] x, char y)
        {
            if (_dict.ContainsKey(y))
            {
                if (null != x)
                {
                    _x.Add(x);
                    _y.Add(_dict[y]);
                }
            }
        }

        public bool LoadSVM()
        {
            return this.LoadSVM(this._svmDataFile);
        }

        public bool LoadSVM(string file)
        {
            try
            {
                _svm = Model.Read(file);
                _range = RangeTransform.Read(RangeFile(file));
                _problem = Problem.Read(ProblemFile(file));
                _x = new List<Node[]>(_problem.X);
                _y = new List<double>(_problem.Y);
                return true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                this.ResetSVM();
                return false;
            }
        }

        public void ResetSVM()
        {
            this._x = new List<Node[]>();
            this._y = new List<double>();
            this._svm = null;
            this._range = null;
            this._problem = null;
        }

        public string AppendFileName(string file, string suffix)
        {

            string fileWithoutExtension = Path.GetFileNameWithoutExtension(file);
            string extension = Path.GetExtension(file);
            string directory = Path.GetDirectoryName(file);
            return string.Format("{0}\\{1}{2}{3}", string.IsNullOrEmpty(directory) ? "." : directory, 
                fileWithoutExtension, suffix, extension);
        }

        public string RangeFile(string svmFile)
        {
            return AppendFileName(svmFile, "_Range");
        }

        public string ProblemFile(string svmFile)
        {
            return AppendFileName(svmFile, "_Problem");
        }

        public void SaveSVM(string file)
        {
            Model.Write(file, _svm);
            RangeTransform.Write(RangeFile(file), _range);
            Problem.Write(ProblemFile(file), _problem);
        }

        public void SaveSVM()
        {
            this.SaveSVM(this._svmDataFile);
        }

        public bool Training()
        {
            if (_x.Count > 0)
            {
                Parameter param = new Parameter();
                param.C = 2;
                param.Gamma = .5;

                _problem = new Problem(_x.Count, _y.ToArray(), _x.ToArray(), this.Dimension);
                _range = Scaling.DetermineRange(_problem);
                Problem p = Scaling.Scale(_problem, _range);
                _svm = Studio.Algorithm.SVM.Training.Train(p, param);
                return true;
            }
            return false;
        }
    }
}
